#include <stdio.h>
#include <stddef.h>

#include <system.h>
#include <sys/alt_irq.h>

#include <io.h>

#include <unistd.h>

static const unsigned char ascii [] = {
  /*      00    01    02    03    04    05    06    07    08    09    0A    0B    0C    0D    0E    0F */
  /*00*/ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',  '`', '\0',
  /*10*/ '\0', '\0', '\0', '\0', '\0',  'q',  '1', '\0', '\0', '\0',  'z',  's',  'a',  'w',  '2', '\0',
  /*20*/ '\0',  'c',  'x',  'd',  'e',  '4',  '3', '\0', '\0',  ' ',  'v',  'f',  't',  'r',  '5', '\0',
  /*30*/ '\0',  'n',  'b',  'h',  'g',  'y',  '6', '\0', '\0', '\0',  'm',  'j',  'u',  '7',  '8', '\0',
  /*40*/ '\0',  ',',  'k',  'i',  'o',  '0',  '9', '\0', '\0',  '.',  '/',  'l',  ';',  'p',  '-', '\0',
  /*50*/ '\0', '\0', '\'', '\0',  '[',  '=', '\0', '\0', '\0', '\0', '\n',  ']', '\0', '\\', '\0', '\0',
  /*60*/ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
  /*70*/ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
  /*80*/ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
  /*90*/ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
  /*A0*/ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
  /*B0*/ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
  /*C0*/ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
  /*D0*/ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
  /*E0*/ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
  /*F0*/ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'
};

enum {BUFFER_LEN = 32};

static unsigned char buffer [BUFFER_LEN] = {'\0'};
static unsigned char head = 0;
static unsigned char tail = 0;

static int break_code = 0;

void ps2_kb_irq_handler (void * context) {
  unsigned char scode = IORD_8DIRECT (KEYBOARD_BASE, 0);
  if (((tail + 1) % BUFFER_LEN) == head) {
    return; /*buffer is full*/
  }

  if (scode == 0xF0) {
    break_code = 1;
  } else {
    if (!break_code) {
      buffer [tail] = ascii [scode];
      tail = (tail + 1) % BUFFER_LEN;
    }

    break_code = 0;
  }

  (void) context;
}

void ps2_kb_init (void) {
  alt_ic_isr_register (KEYBOARD_IRQ_INTERRUPT_CONTROLLER_ID, KEYBOARD_IRQ,
    ps2_kb_irq_handler, NULL, NULL);
}

int ps2_kb_getc (void) {
  while (1) {
    alt_ic_irq_disable (KEYBOARD_IRQ_INTERRUPT_CONTROLLER_ID, KEYBOARD_IRQ);
    if (tail != head) {
      int result = buffer [head];
      head = (head + 1) % BUFFER_LEN;
      alt_ic_irq_enable (KEYBOARD_IRQ_INTERRUPT_CONTROLLER_ID, KEYBOARD_IRQ);
      return result;
    }
    alt_ic_irq_enable (KEYBOARD_IRQ_INTERRUPT_CONTROLLER_ID, KEYBOARD_IRQ);

    usleep (100); /*buffer is empty, wait for 0.1 ms*/
  }
}

int main (void) {
  ps2_kb_init ();
  (void) printf ("Use the Keyboard, Luke...\n");
  while (1) {
    (void) printf ("%c", ps2_kb_getc ());
  }
  return 0;
}
